package de.bk.timecalc.core;

/**
 * Maximum score is 10 - that would be for a correct answer given in zero
 * seconds. Each second you need to calculate the answer costs you 1 point.
 * Answering a time that is slightly off still gives you points.
 * 
 * <ul>
 * <li>1 second too early decreases the score by 0.1</li>
 * <li>2 seconds too early decreases the score by 0.2</li>
 * <li>3 seconds too early decreases the score by 0.4</li>
 * <li>4 seconds too early decreases the score by 0.7</li>
 * <li>5 seconds too early decreases the score by 1</li>
 * <li>6 seconds too early decreases the score by 2</li>
 * <li>7 seconds too early decreases the score by 4</li>
 * <li>8 seconds too early decreases the score by 7</li>
 * <li>9 or more seconds too early: No Points will be given</li>
 * <li>1 second too late decreases the score by 0.2</li>
 * <li>2 seconds too late decreases the score by 0.4</li>
 * <li>3 seconds too late decreases the score by 0.8</li>
 * <li>4 seconds too late decreases the score by 1.4</li>
 * <li>5 seconds too late decreases the score by 2</li>
 * <li>6 seconds too late decreases the score by 4</li>
 * <li>7 seconds too late decreases the score by 8</li>
 * <li>8 or more seconds too late: No Points will be given</li>
 * </ul>
 * 
 * This scoring schema never gives a negative score. Should the score calculated
 * by the rules above be negative, 0 will be returned.
 * 
 * @author Bastian Krol
 * @version $Revision:$ $Date:$ $Author:$
 */
public class DefaultScoringSchema implements IScoringSchema
{
  private static final float MAX_DURATION_MILLIS = 10000F;

  /**
   * {@inheritDoc}
   * 
   * @see de.bk.time.core.IScoringSchema#calculateScore(de.bk.timecalc.core.TimerDirection,
   *      de.bk.time.core.TimeDTO, de.bk.time.core.TimeDTO,
   *      de.bk.time.core.Duration)
   */
  public float calculateScore(TimerDirection timerDirection,
      TimeDTO correctAnswer, TimeDTO givenAnswer, Duration duration)
  {
    if (correctAnswer == null)
    {
      throw new IllegalArgumentException("correctAnswer may not be null");
    }
    if (givenAnswer == null)
    {
      throw new IllegalArgumentException("givenAnswer may not be null");
    }
    if (duration == null)
    {
      throw new IllegalArgumentException("duration may not be null");
    }

    float score;
    if (duration.getMillisecondsSum() >= MAX_DURATION_MILLIS)
    {
      score = 0F;
    }
    else
    {
      score = (MAX_DURATION_MILLIS - duration.getMillisecondsSum()) / 1000;
    }

    if (correctAnswer.equals(givenAnswer))
    {
      return score;
    }
    else
    {
      int difference = correctAnswer.subtract(givenAnswer);
      switch (timerDirection)
      {
        case DOWN:
          // nothing to do
          break;
        case UP:
          difference *= -1;
          break;
        default:
          throw new AssertionError();
      }
      if (difference <= -9 || difference >= 8)
      {
        return 0F;
      }
      switch (difference)
      {
        case -1:
          score -= 0.1F;
          break;
        case -2:
          score -= 0.2F;
          break;
        case -3:
          score -= 0.4F;
          break;
        case -4:
          score -= 0.7F;
          break;
        case -5:
          score -= 1.0F;
          break;
        case -6:
          score -= 2.0F;
          break;
        case -7:
          score -= 4.0F;
          break;
        case -8:
          score -= 8.0F;
          break;
        case 1:
          score -= 0.2F;
          break;
        case 2:
          score -= 0.4F;
          break;
        case 3:
          score -= 0.8F;
          break;
        case 4:
          score -= 1.4F;
          break;
        case 5:
          score -= 2.0F;
          break;
        case 6:
          score -= 4.0F;
          break;
        case 7:
          score -= 8.0F;
          break;
        default:
          throw new RuntimeException("Illegal value for difference: "
              + difference);
      }
      return Math.max(0, score);
    }
  }
}
